/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package espe.ccsoft.orm;

import espe.ccsoft.db.Conexion;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Clase abstracta que permite la conexión con la base de datos.
 *
 * @author SOFTWARE1-DECC-ESPE
 * @version \$Rev.*\$
 * @param <T> Es el tipo de template que se va a utilizar.
 */
public abstract class ORMBase<T>{

    /**
     * Propiedad que guarda una sentencia SQL INSERT.
     */
    protected String insertSql;
    /**
     * Propiedad que guarda una sentencia SQL UPDATE.
     */
    protected String updateSql;
    /**
     * Propiedad que guarda una sentencia SQL DELETE.
     */
    protected String deleteSql;
    /**
     * Propiedad que guarda una sentencia SQL SELECT *.
     */
    protected String findAllSql;
    /**
     * Propiedad que guarda una sentencia SQL SELECT por Primary Key.
     */
    protected String findByPkSql;
    /**
     * Propiedad que guarda una sentencia SQL SELECT.
     */
    protected String findSomeSql;
    /**
     * Propiedad que guarda una sentencia SQL SELECT.
     */
    protected String selectStatement;
    /**
     * Propiedad que inicializa la clase Conexion.
     */
    private final Conexion conexionDB = new Conexion();

    /**
     * Consulta SQL que permite ingresar un elemento en una tabla.
     *
     * @param params Array de objetos de tipo Object, contiene todos los datos
     * necesarios para ingresar en la tabla.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final void insert(final Object[] params) throws SQLException, Exception {
        try (Connection conn = conexionDB.conectar()) {
            try (PreparedStatement stmt = conn.prepareStatement(insertSql)) {
                for (int i = 0; i < params.length; i++) {
                    stmt.setObject(i + 1, params[i]);
                }
                stmt.executeUpdate();
                conn.commit();
            }
            conexionDB.desconectar(conn);
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Consulta SQL que permite actualizar un elemento en una tabla.
     *
     * @param params Array de objetos de tipo Object, contiene todos los datos
     * necesarios para realizar la modificación en la tabla.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final void update(final Object[] params) throws SQLException, Exception {
        try (Connection conn = conexionDB.conectar(); PreparedStatement stmt = conn.prepareStatement(updateSql)) {
            for (int i = 0; i < params.length; i++) {
                stmt.setObject(i + 1, params[i]);
            }
            stmt.executeUpdate();
            conn.commit();
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Consulta SQL que permite eliminar un elemento de una tabla.
     *
     * @param params Array de objetos de tipo Object, contiene todos los datos
     * para eliminar el elemento en la tabla.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final void delete(final Object[] params) throws SQLException, Exception {
        try (Connection conn = conexionDB.conectar()) {
            PreparedStatement stmt = conn.prepareStatement(deleteSql);
            for (int i = 0; i < params.length; i++) {
                stmt.setObject(i + 1, params[i]);
            }
            stmt.executeUpdate();
            conn.commit();
            conexionDB.desconectar(conn);
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Consulta SQL que permite buscar un elemento de una tabla por medio de
     * parámetros.
     *
     * @param params Array de objetos de tipo Object.
     * @return Un objeto encontrado mediante la consulta 'select'.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final T findByPK(final Object[] params) throws SQLException, Exception {
        T object = null;
        Connection conn = conexionDB.conectar();
        ResultSet rs = null;
        try {
            try (PreparedStatement stmt = conn.prepareStatement(findByPkSql)) {
                for (int i = 0; i < params.length; i++) {
                    stmt.setObject(i + 1, params[i]);
                }
                rs = stmt.executeQuery();
                if (rs.next()) {
                    object = this.crearObjeto(rs);
                }
            }
            conexionDB.desconectar(conn);
            return object;
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            conn.close();
            rs.close();
        }
        return null;
    }
    
    /**
     * Consulta SQL que permite obtener todos los elementos de una tabla.
     *
     * @return Una lista del tipo de objeto encontrado mediante la consulta
     * 'select'.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public List<T> findAll() throws SQLException, Exception {
        Connection conn = conexionDB.conectar();
        ResultSet rs = null;
        try {
            List<T> objects;
            try (PreparedStatement stmt = conn.prepareStatement(findAllSql)) {
                rs = stmt.executeQuery();
                objects = new ArrayList();
                while (rs.next()) {
                    objects.add(this.crearObjeto(rs));
                }
            }
            conexionDB.desconectar(conn);
            return objects;
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            conn.close();
            rs.close();
        }
        return null;
    }

    /**
     * Consulta SQL que permite buscar un elemento de una tabla por medio de
     * parámetros.
     *
     * @param params Array de objetos de tipo Object.
     * @return Un objeto encontrado mediante la consulta 'select'.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final List<T> findSome(final Object[] params) throws SQLException, Exception {
        Connection conn = conexionDB.conectar();
        ResultSet rs = null;
        try {

            PreparedStatement stmt = conn.prepareStatement(findSomeSql);
            for (int i = 0; i < params.length; i++) {
                stmt.setObject(i + 1, params[i]);
            }
            rs = stmt.executeQuery();
            List<T> objects = new ArrayList();
            while (rs.next()) {
                objects.add(this.crearObjeto(rs));
            }
            conexionDB.desconectar(conn);
            stmt.close();
            return objects;
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            conn.close();
            rs.close();
        }
        return null;
    }

    /**
     * Consulta SQL que permite buscar un elemento de una tabla.
     *
     * @return Un objeto tipo String, resultado de una sentencia 'select'.
     * @throws SQLException en caso de que se produzca una excepción de SQL.
     */
    public final String executeSelect() throws SQLException, Exception {
        Connection conn = conexionDB.conectar();
        ResultSet rs=null;
        try {
            PreparedStatement stmt = conn.prepareStatement(selectStatement);
            rs = stmt.executeQuery();
            if (rs.next()) {
                return rs.getString(1);
            }
            stmt.close();
            conexionDB.desconectar(conn);
        } catch (SQLException ex) {
            Logger.getLogger(ORMBase.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            conn.close();
            rs.close();
        }

        return null;
    }

    /**
     * Permite crear un Objeto de un tipo que haya sido asignado.
     *
     * @param rs ResultSet de una ejecución de sentencia sql.
     * @return T.
     * @throws SQLException en caso que no encuentre alguna columna en la base
     * de datos.
     */
    protected abstract T crearObjeto(ResultSet rs) throws SQLException;    

    /**
     * @param selectStatement the selectStatement to set
     */
    public void setSelectStatement(String selectStatement) {
        this.selectStatement = selectStatement;
    }
}